#include "PD3DTileMapEngine.h"

namespace pd3d {
	PD3DTileMapEngine::PD3DTileMapEngine(LPDIRECT3DDEVICE9 theDevice, const D3DXVECTOR3 &theScale) : device(theDevice), scale(theScale)
	{
		if (device == NULL)
			throw new string("Error in Render::Invalid device.");
		
		PD3DFileX *arq = new PD3DFileX("tile.x");
		LPD3DXFILEENUMOBJECT enumObject =  arq->GetEnumObject();

		SIZE_T size = 0;
        enumObject->GetChildren(&size);    
        for (SIZE_T counter = 0; counter < size; counter++)
        {
           LPD3DXFILEDATA dataObject = NULL;
           enumObject->GetChild(counter, &dataObject);
           GUID guid;
		   
		   dataObject->GetType(&guid);

	       	LPCVOID temp;
	       
			if (FAILED(dataObject->Lock(&size, &temp)))
		         throw new string("GetData::Lock()");
	
	        if(guid == GUID_MAP_INFO){
				memcpy(&info, temp, sizeof(MAP_INFO));
	        }
			else if(guid == GUID_TILE)
	        {
				memcpy(&tiles, temp, sizeof(TILE));
	        }
			else if(guid == GUID_MAPVERTEX)
	        {
				memcpy(&triangles, temp, sizeof(MAPVERTEX));      
	        }
	        else
	        {
		        throw new string("GetData::Unknown data type");
	        }

	        dataObject->Unlock();
	        dataObject->Release();
         }
		initTiles();
	}

	PD3DTileMapEngine::~PD3DTileMapEngine(void)
	{
		RELEASE(vertexBuffer);
	}

	void PD3DTileMapEngine::render(const int *tileMap, int mapSize, bool frustumCulling, const int *lightMap, bool lightFrustumCulling)
	{
		int sqrtMapSize = static_cast<int>(sqrt(static_cast<double>(mapSize)));
		int mapIndex = 0;
		int tileIndex = 0;
		int lightIndex = 0;
		bool up = false, showTextureLight = true;
		float x = 0.0f, y = 0.0f, z = 0.0f;

		D3DXMATRIX matRot, matTrans, matScale;
		ZeroMemory(&matTrans, sizeof(D3DMATRIX));
		ZeroMemory(&matRot, sizeof(D3DMATRIX));
		ZeroMemory(&matScale, sizeof(D3DMATRIX));

		if (frustumCulling || lightFrustumCulling) frustum.Refresh(device);

		nRenderedTitles = 0;
		nRenderedLights = 0;

		device->SetStreamSource(0, renderData.vertexBuffer, 0, renderData.vertexSize);
		device->SetFVF(renderData.FVF);

		if (tileMap == NULL)
			throw new string("Error in Render::Invalid tile map.");

		for (int i = 0; i < sqrtMapSize; i++)
		{
			for (int j = 0; j < sqrtMapSize; j++)
			{
				up = (tileMap[mapIndex] < 0);

				if (lightMap != NULL && lightMap[mapIndex] >= 0 && lightMap[mapIndex] < N_LIGHTS)
				{
					float lightHeigth = 1.0f * scale.y;
					if (up) lightHeigth += scale.y;

					x = ((float) j * 2.0f) * scale.x;
					y = lightHeigth;
					z = -((float) i * 2.0f) * scale.z;

					D3DLIGHT9 light(lights[lightMap[mapIndex]]);
					light.Position.x = x;
					light.Position.y = y;
					light.Position.z = z;

					device->SetLight(lightIndex, &light);
					if (!lightFrustumCulling || frustum.CheckSphere(Vector3(x, y, z), light.Range + scale.x))
					{
						nRenderedLights++;
						device->LightEnable(lightIndex, TRUE);
						showTextureLight = true;
					}
					else
					{
						device->LightEnable(lightIndex, FALSE);
						showTextureLight = false;
					}
					lightIndex++;
				}

				if (tileMap[mapIndex] != renderData.skipValue)
				{

					tileIndex = abs(tileMap[mapIndex]);				

					x = ((float) j * 2.0f) * scale.x;
					y = tiles[tileIndex].height * scale.y + ((up) ? scale.y : 0.0f);
					z = -((float) i * 2.0f) * scale.z;

					if (!frustumCulling || frustum.CheckRectangle(Vector3(x, y, z), Vector3(scale.x, scale.y, scale.z)))
					{					
						nRenderedTitles++;

						D3DXMatrixRotationY(&matRot, tiles[tileIndex].angle);
						D3DXMatrixScaling(&matScale, scale.x, scale.y, scale.z);						
						D3DXMatrixTranslation(&matTrans, x, y, z);
						device->SetTransform(D3DTS_WORLD, &(matRot * matScale * matTrans));

						device->SetTexture(0, surfaces[tiles[tileIndex].textureIndex].tile);

						if (showTextureLight && lightMap != NULL)
						{
							device->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATEALPHA_ADDCOLOR);
							device->SetTexture(1, lightSurfaces[lightMap[mapIndex]].tile);
						}
						else
						{
							device->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
							device->SetTexture(1, NULL);
						}

						if (FAILED(device->DrawPrimitive(D3DPT_TRIANGLESTRIP,
							tiles[tileIndex].firstVertexIndex,
							tiles[tileIndex].numFaces)))
						{
							throw new string("Error in Render::DrawPrimitive().");
						}
					}
				}
				mapIndex++;
			}
		}
	}

	int PD3DTileMapEngine::getNRenderedTiles(void)
	{
		return nRenderedTitles;
	}

	int PD3DTileMapEngine::getNRenderedLights(void)
	{
		return nRenderedLights;
	}

	void PD3DTileMapEngine::initTiles(void)
	{
		
		MAPVERTEX triangles[32] =
		{
			// X     Y     Z     NX    NY    NZ    U     V     U1    V1
			{ 1.0f,-1.0f,-1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f},
			{-1.0f,-1.0f,-1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
			{ 1.0f,-1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.5f, 1.0f, 0.5f},
			{-1.0f,-1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f},

			{ 1.0f, 1.0f, 1.0f,	0.0f, 0.0f,-1.0f, 1.0f, 0.0f, 1.0f, 0.0f},
			{-1.0f, 1.0f, 1.0f,	0.0f, 0.0f,-1.0f, 0.0f, 0.0f, 0.0f, 0.0f},

			{-1.0f, 1.0f, 1.0f,	1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
			{-1.0f, 1.0f, 1.0f,	1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f},

			{-1.0f, 1.0f,-1.0f,	1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
			{-1.0f, 1.0f, 1.0f,	1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f},
			{-1.0f,-1.0f,-1.0f,	0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f},
			{-1.0f,-1.0f, 1.0f,	0.0f, 1.0f, 0.0f, 1.0f, 0.5f, 1.0f, 0.5f},

			// X     Y     Z     NX    NY    NZ    U     V     U1    V1
			{ 1.0f,-1.0f,-1.0f,-1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f},
			{-1.0f,-2.0f,-1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
			{ 1.0f,-1.0f, 1.0f,-1.0f, 1.0f, 0.0f, 1.0f, 0.5f, 1.0f, 0.5f},
			{-1.0f,-2.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f},

			{ 1.0f, 1.0f, 1.0f,	0.0f, 0.0f,-1.0f, 1.0f, 0.0f, 1.0f, 0.0f},
			{-1.0f, 0.0f, 1.0f,	0.0f, 0.0f,-1.0f, 0.0f, 0.0f, 0.0f, 0.0f},

			// X     Y     Z     NX    NY    NZ    U0    V0    U1    V1
			{ 1.0f,-2.0f,-1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f},
			{-1.0f,-1.0f,-1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
			{ 1.0f,-2.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.5f, 1.0f, 0.5f},
			{-1.0f,-1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f},

			{ 1.0f, 0.0f, 1.0f,	0.0f, 0.0f,-1.0f, 1.0f, 0.0f, 1.0f, 0.0f},
			{-1.0f, 1.0f, 1.0f,	0.0f, 0.0f,-1.0f, 0.0f, 0.0f, 0.0f, 0.0f}
		};

		if (FAILED(device->CreateVertexBuffer(32 * sizeof(MAPVERTEX),
			D3DUSAGE_WRITEONLY,
			MAPVERTEX_FVF,
			D3DPOOL_DEFAULT,
			&vertexBuffer, NULL)))
		{
			throw new string("Error in InitTiles::CreateVertexBuffer().");
		}

		void *offset;
		vertexBuffer->Lock(0, 0, (void**)&offset, 0);
		memcpy(offset, triangles, sizeof(triangles));
		vertexBuffer->Unlock();

		// floor
		tiles[0].firstVertexIndex = 0;
		tiles[0].numFaces = 2;
		tiles[0].textureIndex = 0;
		// floor - wall
		tiles[1].firstVertexIndex = 0;
		tiles[1].numFaces = 4;
		tiles[1].textureIndex = 3;
		tiles[2].firstVertexIndex = 0;
		tiles[2].numFaces = 4;
		tiles[2].textureIndex = 3;
		tiles[2].angle = D3DX_PI / 2.0f;
		tiles[3].firstVertexIndex = 0;
		tiles[3].numFaces = 4;
		tiles[3].textureIndex = 3;
		tiles[3].angle = D3DX_PI;
		tiles[4].firstVertexIndex = 0;
		tiles[4].numFaces = 4;
		tiles[4].textureIndex = 3;
		tiles[4].angle = D3DX_PI + (D3DX_PI / 2.0f);
		// floor - edge
		tiles[5].firstVertexIndex = 0;
		tiles[5].numFaces = 10;
		tiles[5].textureIndex = 4;
		tiles[6].firstVertexIndex = 0;
		tiles[6].numFaces = 10;
		tiles[6].textureIndex = 4;
		tiles[6].angle = D3DX_PI / 2.0f;
		tiles[7].firstVertexIndex = 0;
		tiles[7].numFaces = 10;
		tiles[7].textureIndex = 4;
		tiles[7].angle = D3DX_PI;
		tiles[8].firstVertexIndex = 0;
		tiles[8].numFaces = 10;
		tiles[8].textureIndex = 4;
		tiles[8].angle = D3DX_PI + (D3DX_PI / 2.0f);
		// curved floor - wall
		tiles[9].firstVertexIndex = 12;
		tiles[9].numFaces = 4;
		tiles[9].textureIndex = 1;
		tiles[9].height = 1;
		tiles[10].firstVertexIndex = 12;
		tiles[10].numFaces = 4;
		tiles[10].textureIndex = 1;
		tiles[10].angle = D3DX_PI / 2.0f;
		tiles[10].height = 1;
		tiles[11].firstVertexIndex = 12;
		tiles[11].numFaces = 4;
		tiles[11].textureIndex = 1;
		tiles[11].angle = D3DX_PI;
		tiles[11].height = 1;
		tiles[12].firstVertexIndex = 12;
		tiles[12].numFaces = 4;
		tiles[12].textureIndex = 1;
		tiles[12].angle = D3DX_PI + (D3DX_PI / 2.0f);
		tiles[12].height = 1;
		// curved floor - wall
		tiles[13].firstVertexIndex = 18;
		tiles[13].numFaces = 4;
		tiles[13].textureIndex = 2;
		tiles[13].height = 1;
		tiles[14].firstVertexIndex = 18;
		tiles[14].numFaces = 4;
		tiles[14].textureIndex = 2;
		tiles[14].angle = D3DX_PI / 2.0f;
		tiles[14].height = 1;
		tiles[15].firstVertexIndex = 18;
		tiles[15].numFaces = 4;
		tiles[15].textureIndex = 2;
		tiles[15].angle = D3DX_PI;
		tiles[15].height = 1;
		tiles[16].firstVertexIndex = 18;
		tiles[16].numFaces = 4;
		tiles[16].textureIndex = 2;
		tiles[16].angle = D3DX_PI + (D3DX_PI / 2.0f);
		tiles[16].height = 1;
		// curved floor
		tiles[17].firstVertexIndex = 12;
		tiles[17].numFaces = 2;
		tiles[17].textureIndex = 5;
		tiles[17].height = 1;
		tiles[18].firstVertexIndex = 12;
		tiles[18].numFaces = 2;
		tiles[18].textureIndex = 5;
		tiles[18].angle = D3DX_PI / 2.0f;
		tiles[18].height = 1;
		tiles[19].firstVertexIndex = 12;
		tiles[19].numFaces = 2;
		tiles[19].textureIndex = 5;
		tiles[19].angle = D3DX_PI;
		tiles[19].height = 1;
		tiles[20].firstVertexIndex = 12;
		tiles[20].numFaces = 2;
		tiles[20].textureIndex = 5;
		tiles[20].angle = D3DX_PI + (D3DX_PI / 2.0f);
		tiles[20].height = 1;
		//piso elevado
		tiles[21].firstVertexIndex = 0;
		tiles[21].numFaces = 2;
		tiles[21].textureIndex = 0;
		tiles[21].height = 1;
		//piso parede elevado
		tiles[22].firstVertexIndex = 0;
		tiles[22].numFaces = 4;
		tiles[22].textureIndex = 3;
		tiles[22].height = 1;
		tiles[23].firstVertexIndex = 0;
		tiles[23].numFaces = 4;
		tiles[23].textureIndex = 3;
		tiles[23].angle = D3DX_PI / 2.0f;
		tiles[23].height = 1;
		tiles[24].firstVertexIndex = 0;
		tiles[24].numFaces = 4;
		tiles[24].textureIndex = 3;
		tiles[24].angle = D3DX_PI;
		tiles[24].height = 1;
		tiles[25].firstVertexIndex = 0;
		tiles[25].numFaces = 4;
		tiles[25].textureIndex = 3;
		tiles[25].angle = D3DX_PI + (D3DX_PI / 2.0f);
		tiles[25].height = 1;
		//piso canto elevado
		tiles[26].firstVertexIndex = 0;
		tiles[26].numFaces = 10;
		tiles[26].textureIndex = 4;
		tiles[26].height = 1;
		tiles[27].firstVertexIndex = 0;
		tiles[27].numFaces = 10;
		tiles[27].textureIndex = 4;
		tiles[27].angle = D3DX_PI / 2.0f;
		tiles[27].height = 1;
		tiles[28].firstVertexIndex = 0;
		tiles[28].numFaces = 10;
		tiles[28].textureIndex = 4;
		tiles[28].angle = D3DX_PI;
		tiles[28].height = 1;
		tiles[29].firstVertexIndex = 0;
		tiles[29].numFaces = 10;
		tiles[29].textureIndex = 4;
		tiles[29].angle = D3DX_PI + (D3DX_PI / 2.0f);
		tiles[29].height = 1;

		renderData.FVF = MAPVERTEX_FVF;
		renderData.vertexBuffer = vertexBuffer;
		renderData.vertexSize = sizeof(MAPVERTEX);
		renderData.skipValue = 99;

		if (FAILED(D3DXCreateTextureFromFile(device, L"tiles\\tile00.jpg", &surfaces[0].tile)))
			throw new string ("Error in InitTiles::D3DXCreateTextureFromFile() - tile00.jpg");

		if (FAILED(D3DXCreateTextureFromFile(device, L"tiles\\tile01.jpg", &surfaces[1].tile)))
			throw new string ("Error in InitTiles::D3DXCreateTextureFromFile() - tile01.jpg");

		if (FAILED(D3DXCreateTextureFromFile(device, L"tiles\\tile02.jpg", &surfaces[2].tile)))
			throw new string ("Error in InitTiles::D3DXCreateTextureFromFile() - tile02.jpg");

		if (FAILED(D3DXCreateTextureFromFile(device, L"tiles\\tile03.jpg", &surfaces[3].tile)))
			throw new string ("Error in InitTiles::D3DXCreateTextureFromFile() - tile03.jpg");

		if (FAILED(D3DXCreateTextureFromFile(device, L"tiles\\tile04.jpg", &surfaces[4].tile)))
			throw new string ("Error in InitTiles::D3DXCreateTextureFromFile() - tile04.jpg");

		if (FAILED(D3DXCreateTextureFromFile(device, L"tiles\\tile05.jpg", &surfaces[5].tile)))
			throw new string ("Error in InitTiles::D3DXCreateTextureFromFile() - tile04.jpg");

		float scaleMean = (scale.x + scale.y + scale.z) / 3.0f;
		ZeroMemory(&lights[0], sizeof(D3DLIGHT9));
		lights[0].Type = D3DLIGHT_POINT;
		lights[0].Attenuation0 = 1.0f;
		lights[0].Diffuse.a = 1.0f;
		lights[0].Diffuse.r = 1.00f * scaleMean;
		lights[0].Diffuse.g = 0.00f * scaleMean;
		lights[0].Diffuse.b = 0.00f * scaleMean;
		lights[0].Range = 3.0f * scaleMean;

		ZeroMemory(&lights[1], sizeof(D3DLIGHT9));
		lights[1].Type = D3DLIGHT_POINT;
		lights[1].Attenuation0 = 1.0f;
		lights[1].Diffuse.a = 1.0f;
		lights[1].Diffuse.r = 0.00f * scaleMean;
		lights[1].Diffuse.g = 1.00f * scaleMean;
		lights[1].Diffuse.b = 0.00f * scaleMean;
		lights[1].Range = 3.0f * scaleMean;

		ZeroMemory(&lights[2], sizeof(D3DLIGHT9));
		lights[2].Type = D3DLIGHT_POINT;
		lights[2].Attenuation0 = 1.0f;
		lights[2].Diffuse.a = 1.0f;
		lights[2].Diffuse.r = 0.00f * scaleMean;
		lights[2].Diffuse.g = 0.00f * scaleMean;
		lights[2].Diffuse.b = 1.00f * scaleMean;
		lights[2].Range = 3.0f * scaleMean;

		ZeroMemory(&lights[3], sizeof(D3DLIGHT9));
		lights[3].Type = D3DLIGHT_POINT;
		lights[3].Attenuation0 = 1.0f;
		lights[3].Diffuse.a = 1.0f;
		lights[3].Diffuse.r = 0.00f * scaleMean;
		lights[3].Diffuse.g = 1.00f * scaleMean;
		lights[3].Diffuse.b = 1.00f * scaleMean;
		lights[3].Range = 3.0f * scaleMean;
		if (FAILED(D3DXCreateTextureFromFile(device, L"lightmaps\\lit0.bmp", &lightSurfaces[0].tile)))
			throw new string ("Error in InitTiles::D3DXCreateTextureFromFile() - lit0.bmp");

		if (FAILED(D3DXCreateTextureFromFile(device, L"lightmaps\\lit1.bmp", &lightSurfaces[1].tile)))
			throw new string ("Error in InitTiles::D3DXCreateTextureFromFile() - lit1.bmp");

		if (FAILED(D3DXCreateTextureFromFile(device, L"lightmaps\\lit2.bmp", &lightSurfaces[2].tile)))
			throw new string ("Error in InitTiles::D3DXCreateTextureFromFile() - lit2.bmp");
	}
}